package com.xxtx.trade.id.service.impl;

import com.xxtx.trade.common.enums.FormNoTypeEnum;
import com.xxtx.trade.common.exception.GlobalException;
import com.xxtx.trade.common.util.FormNoSerialUtil;
import com.xxtx.trade.common.util.model.ResultMsg;
import com.xxtx.trade.id.service.IdService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 功能描述:
 *
 * @Params:
 * @Author: xuliang
 * @email: northly@live.com
 * @Date: 2020/3/7 13:57
 */
@Slf4j
@Service
public class IdServiceImpl implements IdService {

    /**
     * redis 服务
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private Map<Integer, Lock> mapLocks = new ConcurrentHashMap<>();

    private int locksNumer = 6;

    /**
     * 根据单据编号类型 生成单据编号
     *
     * @param prefix 单据编号前缀
     * @date 2019/1/1
     */
    @Override
    public String sequence(String prefix) {

        log.info("获取sequence by:{} ", prefix);
        // 获得单号前缀
        // 格式 固定前缀 +时间前缀 示例 ：YF20190101
        FormNoTypeEnum formNoTypeEnum = FormNoTypeEnum.find(prefix);

        if (formNoTypeEnum == null) {
            log.info("not exists current prefix:{} ", prefix);
            throw new GlobalException(ResultMsg.PREFIX_IS_NOT_EXISTS);
        }

        String formNoPrefix = FormNoSerialUtil.getFormNoPrefix(formNoTypeEnum);
        Integer key = formNoPrefix.hashCode() % locksNumer;
        doLock(key);
        // 获得缓存key
        String cacheKey = FormNoSerialUtil.getCacheKey(formNoPrefix);
        // 获得当日自增数
        Long incrementalSerial = getRedisSequence(cacheKey);

        if (incrementalSerial.compareTo(1L) == 0) {
            // 当序列号等于1时，设置key过期时间 // 设置失效时间2天
            stringRedisTemplate.expire(formNoPrefix, 48, TimeUnit.HOURS);
        }
        // 组合单号并补全流水号
        String serialWithPrefix = FormNoSerialUtil.completionSerial(formNoPrefix, incrementalSerial, formNoTypeEnum);
        // 补全随机数
        String no = FormNoSerialUtil.completionRandom(serialWithPrefix, formNoTypeEnum);

        try {
            return no;
        } catch (Exception ex) {

        } finally {
            unLock(key);
        }
        return null;
    }

    @Override
    public List<String> sequence(String prefix, int count) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            String seq = sequence(prefix);
            if(!StringUtils.isEmpty(seq)) {
                list.add(sequence(prefix));
            }
        }
        return list;
    }

    /**
     * 解锁
     */
    private void unLock(Integer key) {
        Lock lock = mapLocks.get(key);
        if (lock != null && Thread.holdsLock(lock)) {
            lock.unlock();
        }
    }

    /**
     * 加锁
     */
    private void doLock(Integer key) {
        Lock newLock = new ReentrantLock();
        Lock oldLock = mapLocks.putIfAbsent(key, newLock); // 如果key存在，返回老锁，不存在返回空
        if (oldLock == null) {
            newLock.tryLock();
        } else {
            oldLock.tryLock();
        }

    }

    private Long getRedisSequence(String key) {
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long sequence = 0L;
        try {
            if (redisAtomicLong.get() == 0) {
                redisAtomicLong.getAndSet(0L);
            }
            sequence = redisAtomicLong.incrementAndGet();
            if (sequence == null) {
                return 0L;
            }
        } catch (Exception ex) {
            log.error("Failed to get sequence.", ex);
        }
        return sequence;
    }

}
